package com.sqi.reactive

import com.google.common.collect.Lists
import com.sjdq.imp.createView
import com.sqi.reactive.common.util.lineToHump
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.r2dbc.core.DatabaseClient
import java.io.File
import java.time.LocalDate

/**
 * @author sjl
 * @date 2020/5/27
 */
@SpringBootTest(classes = [SqiReactiveApplication::class])
class CodeGenerator {
    @Autowired
    lateinit var client: DatabaseClient
    val sql = """SELECT
                TB.TABLE_SCHEMA,    -- 模式
                TB.TABLE_NAME,      -- 表名
                TB.TABLE_COMMENT,   -- 表名注释
                COL.COLUMN_NAME,    -- 字段名
                COL.DATA_TYPE,    -- 字段类型
                COL.CHARACTER_MAXIMUM_LENGTH, -- 字段长度
                COL.COLUMN_KEY, -- 字段键
                COL.COLUMN_COMMENT  -- 字段注释
            FROM
                INFORMATION_SCHEMA.TABLES TB,
                INFORMATION_SCHEMA.COLUMNS COL 
            WHERE
                TB.TABLE_NAME = :tableName 
                AND TB.TABLE_SCHEMA= '$DATABASE_NAME'
                AND TB.TABLE_NAME = COL.TABLE_NAME 
                AND TB.TABLE_SCHEMA = COL.TABLE_SCHEMA
            ORDER BY
                TB.TABLE_NAME,COL.ORDINAL_POSITION"""

    @Test
    fun codeGenerator() {
        infos.forEach { info ->
            val tableName = info.first
            val packageName = info.second
            val packageComment = info.third
            val tableInfo = TableInfo(tableName = tableName,
                    packageName = packageName,
                    packageComment = packageComment,
                    entityName = tableName.entityName())
            val columnInfoList = Lists.newArrayList<ColumnInfo>()
            tableInfo.columnInfoList = columnInfoList
            client.execute(sql).bind("tableName", tableName)
                    .fetch().all()
                    .collectList().block()?.forEach {
                        val columnInfo = ColumnInfo(
                                columnName = it["COLUMN_NAME"] as String?,
                                columnType = it["DATA_TYPE"] as String?,
                                columnLength = (it["CHARACTER_MAXIMUM_LENGTH"] as Number?)?.toLong(),
                                columnKey = it["COLUMN_KEY"] as String?,
                                fieldName = (it["COLUMN_NAME"] as String?)?.lineToHump(),
                                fieldType = (it["DATA_TYPE"] as String?)?.fieldType(),
                                columnComment = it["COLUMN_COMMENT"] as String?
                                        ?: (it["COLUMN_NAME"] as String?)?.lineToHump()
                        )
                        columnInfoList.add(columnInfo)
                        if (columnInfo.isPrimaryKey()) {
                            columnInfo.columnComment = "ID"
                            tableInfo.pkType = columnInfo.fieldType?.replace("?", "")
                        }
                        if (columnInfo.fieldName == "createTime") {
                            columnInfo.columnComment = "创建时间"
                        }
                        when (columnInfo.fieldType) {
                            "Timestamp?" -> {
                                tableInfo.importStrings.add("import java.sql.Timestamp\n")
                            }
                            "Date?" -> {
                                tableInfo.importStrings.add("import java.sql.Date\n")
                            }
                            "Time?" -> {
                                tableInfo.importStrings.add("import java.sql.Time\n")
                            }
                        }
                        tableInfo.tableComment = it["TABLE_COMMENT"] as String?
                        tableInfo.tableSchema = it["TABLE_SCHEMA"] as String?
                    }
            tableInfo
                    .apply(TableInfo::createEntity)
                    .apply(TableInfo::createRepository)
                    .apply(TableInfo::createService)
                    .apply(TableInfo::createController)
                    .apply(TableInfo::createView)
        }
    }
}

//--------------
val author = System.getenv("user_name") ?: System.getProperty("user.name")!!
val infos = arrayOf(Triple("imp_user", "user", "用户管理"))

/**
 * 代码生成器常量
 */
val BASE_DIR = File("").absolutePath + "/src/main/kotlin/com/sqi/reactive"
const val BASE_PACKAGE = "com.sqi.reactive"
const val BASE_MODULE = "imp"
val WEB_BASE_DIR = File(File("").absolutePath).parent + "/web/src"
val WEB_VIEWS_BASE_DIR = "$WEB_BASE_DIR/views/$BASE_MODULE"
const val DATABASE_NAME = "sqi"


fun TableInfo.createController() {
    val code = """
package $BASE_PACKAGE.${this.packageName}.controller

import $BASE_PACKAGE.common.controller.BaseCrudController
import $BASE_PACKAGE.${this.packageName}.service.${this.entityName}Service
import $BASE_PACKAGE.${this.packageName}.entity.${this.entityName}
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

/**
 * @author $author
 * @date ${local.year}/${local.month.value}/${local.dayOfMonth}
 */
@RestController
@RequestMapping("/${this.packageName}/${this.entityName?.decapitalize()}")
class ${this.entityName}Controller : BaseCrudController<${this.entityName}, ${this.pkType}, ${this.entityName}Service>()
    """.trimIndent()
    val controllerDir = File(BASE_DIR, "${packageName}/controller")
    if (!controllerDir.exists()) {
        controllerDir.mkdirs()
    }
    val controllerFile = File(controllerDir, "${this.entityName}Controller.kt")
    controllerFile.writeText(code)
}

fun TableInfo.createService() {
    val code = """
package $BASE_PACKAGE.${this.packageName}.service

import $BASE_PACKAGE.common.service.BaseService
import $BASE_PACKAGE.${this.packageName}.entity.${this.entityName}
import $BASE_PACKAGE.${this.packageName}.repository.${this.entityName}Repository
import org.springframework.stereotype.Service

/**
 * @author $author
 * @date ${local.year}/${local.month.value}/${local.dayOfMonth}
 */
@Service
class ${this.entityName}Service : BaseService<${this.entityName}, ${this.entityName}Repository, ${this.pkType}>()
    """.trimIndent()
    val serviceDir = File(BASE_DIR, "${packageName}/service")
    if (!serviceDir.exists()) {
        serviceDir.mkdirs()
    }
    val serviceFile = File(serviceDir, "${this.entityName}Service.kt")
    serviceFile.writeText(code)
}

fun TableInfo.createRepository() {
    val code = """
package $BASE_PACKAGE.${this.packageName}.repository

import $BASE_PACKAGE.${this.packageName}.entity.${this.entityName}
import $BASE_PACKAGE.common.repository.DynamicQueryRepository
import org.springframework.stereotype.Repository

/**
 * @author $author
 * @date ${local.year}/${local.month.value}/${local.dayOfMonth}
 */
@Repository
interface ${this.entityName}Repository : DynamicQueryRepository<${this.entityName}, ${this.pkType}>
    """.trimIndent()
    val repositoryDir = File(BASE_DIR, "${packageName}/repository")
    if (!repositoryDir.exists()) {
        repositoryDir.mkdirs()
    }
    val repositoryFile = File(repositoryDir, "${this.entityName}Repository.kt")
    repositoryFile.writeText(code)
}

fun TableInfo.createEntity() {
    val code = """
package $BASE_PACKAGE.${this.packageName}.entity

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Column
import org.springframework.data.relational.core.mapping.Table
${this.importStrings.joinToString("\n")}
/**
 * ${this.tableComment} 
 *
 * @author $author
 * @date ${local.year}/${local.month.value}/${local.dayOfMonth}
 */
@Table(value = "${this.tableName}")
data class ${this.entityName}(
   ${
        this.columnInfoList?.joinToString(",\n") {
            "\t\t/**\n" +
                    "\t\t * ${it.columnComment}\n" +
                    "\t\t */\n" +
                    "${
                        if (it.isPrimaryKey()) {
                            "\t\t@Id\n"
                        } else {
                            "\t\t@Column(value = \"${it.columnName}\")\n"
                        }
                    }\t\tvar ${it.fieldName}: ${it.fieldType} = null"
        }
    }
)""".trim()
    val entityDir = File(BASE_DIR, "${packageName}/entity")
    if (!entityDir.exists()) {
        entityDir.mkdirs()
    }
    val entityFile = File(entityDir, "${this.entityName}.kt")
    entityFile.writeText(code)
}

val local: LocalDate = LocalDate.now()
fun String.entityName(): String {
    return if (this.startsWith("t_") || this.startsWith("r_") || this.startsWith("v_")) {
        this.substring(2).lineToHump().capitalize()
    } else {
        this.lineToHump().capitalize()
    }
}

fun String.fieldType() = when (this) {
    "int" -> "Long?"
    "float", "double", "decimal", "real" -> "Double?"
    "datetime", "timestamp" -> "Timestamp?"
    "date" -> "Date?"
    "time" -> "Time?"
    else -> "String?"
}


data class TableInfo(
        var tableSchema: String? = null,
        var tableName: String? = null,
        var entityName: String? = null,
        var tableComment: String? = null,
        var columnInfoList: List<ColumnInfo>? = null,
        var packageName: String? = null,
        var packageComment: String? = null,
        var importStrings: MutableList<String> = Lists.newArrayList(),
        var pkType: String? = null
)

data class ColumnInfo(
        var columnName: String? = null,
        var columnType: String? = null,
        var columnLength: Long? = null,
        var columnComment: String? = null,
        var columnKey: String? = null,
        var fieldName: String? = null,
        var fieldType: String? = null
) {
    fun isPrimaryKey() = "PRI" == columnKey
}
